Ceļvedis par TypeScript generics sarežģītu datu tipu pārvaldībai. Apgūstiet sintaksi, priekšrocības un labāko praksi robustām globālām lietojumprogrammām.
TypeScript Generics: Sarežģītu datu tipu apgūšana robustām lietojumprogrammām
TypeScript, kas ir JavaScript virskopa, ļauj izstrādātājiem rakstīt robustāku un uzturējamāku kodu, izmantojot statisko tipēšanu. Viena no tās jaudīgākajām funkcijām ir generics (vispārīgie tipi), kas ļauj rakstīt kodu, kurš var darboties ar dažādiem datu tipiem, vienlaikus saglabājot tipu drošību. Šis ceļvedis sniedz visaptverošu ieskatu TypeScript generics, koncentrējoties uz to pielietojumu sarežģītiem datu tipiem globālās programmatūras izstrādes kontekstā.
Kas ir Generics?
Generics nodrošina veidu, kā rakstīt atkārtoti lietojamu kodu, kas var darboties ar dažādiem tipiem. Tā vietā, lai rakstītu atsevišķas funkcijas vai klases katram tipam, ko vēlaties atbalstīt, jūs varat uzrakstīt vienu funkciju vai klasi, kas izmanto tipu parametrus. Šie tipu parametri ir vietturi faktiskajiem tipiem, kas tiks izmantoti, kad funkcija vai klase tiks izsaukta vai instancēta. Tas ir īpaši noderīgi, strādājot ar sarežģītām datu struktūrām, kurās datu tips var atšķirties.
Generics izmantošanas priekšrocības
- Koda atkārtota izmantošana: Uzrakstiet kodu vienreiz un izmantojiet to ar dažādiem tipiem. Tas samazina koda dublēšanos un padara jūsu koda bāzi vieglāk uzturējamu.
- Tipu drošība: Generics ļauj TypeScript kompilatoram nodrošināt tipu drošību kompilēšanas laikā. Tas palīdz novērst izpildlaika kļūdas, kas saistītas ar tipu neatbilstību.
- Uzlabota lasāmība: Generics padara jūsu kodu lasāmāku, skaidri norādot tipus, ar kuriem jūsu funkcijas un klases ir paredzētas darbam.
- Uzlabota veiktspēja: Dažos gadījumos generics var uzlabot veiktspēju, jo kompilators var optimizēt ģenerēto kodu, pamatojoties uz konkrētajiem izmantotajiem tipiem.
Generics pamatsintakse
Generics pamatsintakse ietver leņķa iekavu (< >) izmantošanu, lai deklarētu tipa parametrus. Šiem tipa parametriem parasti tiek doti nosaukumi T
, K
, V
utt., bet jūs varat izmantot jebkuru derīgu identifikatoru. Šeit ir vienkāršs vispārīgas funkcijas piemērs:
function identity<T>(arg: T): T {
return arg;
}
let myString: string = identity<string>("hello");
let myNumber: number = identity<number>(123);
let myBoolean: boolean = identity<boolean>(true);
console.log(myString); // Output: hello
console.log(myNumber); // Output: 123
console.log(myBoolean); // Output: true
Šajā piemērā <T>
deklarē tipa parametru ar nosaukumu T
. Funkcija identity
pieņem argumentu ar tipu T
un atgriež vērtību ar tipu T
. Izsaucot funkciju, jūs varat skaidri norādīt tipa parametru (piemēram, identity<string>
) vai ļaut TypeScript to noteikt, pamatojoties uz argumenta tipu.
Darbs ar sarežģītiem datu tipiem
Generics kļūst īpaši vērtīgi, strādājot ar sarežģītiem datu tipiem, piemēram, masīviem, objektiem un saskarnēm. Apskatīsim dažus izplatītus scenārijus:
Vispārīgie masīvi
Jūs varat izmantot generics, lai izveidotu funkcijas vai klases, kas darbojas ar dažādu tipu masīviem:
function arrayToString<T>(arr: T[]): string {
return arr.join(", ");
}
let numberArray: number[] = [1, 2, 3, 4, 5];
let stringArray: string[] = ["apple", "banana", "cherry"];
console.log(arrayToString(numberArray)); // Output: 1, 2, 3, 4, 5
console.log(arrayToString(stringArray)); // Output: apple, banana, cherry
Šeit funkcija arrayToString
pieņem masīvu ar tipu T[]
un atgriež masīva virknes attēlojumu. Šī funkcija darbojas ar jebkura tipa masīviem, padarot to ļoti atkārtoti lietojamu.
Vispārīgie objekti
Generics var izmantot arī, lai definētu funkcijas vai klases, kas darbojas ar dažādu formu objektiem:
interface Person {
name: string;
age: number;
country: string; // Pievienota valsts globālam kontekstam
}
interface Product {
id: number;
name: string;
price: number;
currency: string; // Pievienota valūta globālam kontekstam
}
function displayInfo<T extends { name: string }>(item: T): void {
console.log(`Name: ${item.name}`);
}
let person: Person = { name: "Alice", age: 30, country: "USA" };
let product: Product = { id: 1, name: "Laptop", price: 1200, currency: "USD" };
displayInfo(person); // Output: Name: Alice
displayInfo(product); // Output: Name: Laptop
Šajā piemērā funkcija displayInfo
pieņem objektu ar tipu T
, kuram jābūt īpašībai name
ar tipu string. Klauzula extends { name: string }
ir ierobežojums, kas nosaka minimālās prasības tipa parametram T
. Tas nodrošina, ka funkcija var droši piekļūt īpašībai name
.
Padziļināta Generics lietošana
TypeScript generics piedāvā arī sarežģītākas funkcijas, kas ļauj jums izveidot vēl elastīgāku un jaudīgāku kodu. Apskatīsim dažas no šīm funkcijām:
Vairāki tipu parametri
Jūs varat definēt funkcijas vai klases ar vairākiem tipu parametriem:
function merge<T, U>(obj1: T, obj2: U): T & U {
return { ...obj1, ...obj2 };
}
interface Name {
firstName: string;
}
interface Age {
age: number;
}
const person: Name = { firstName: "Bob" };
const details: Age = { age: 42 };
const merged = merge(person, details);
console.log(merged.firstName); // Output: Bob
console.log(merged.age); // Output: 42
Funkcija merge
pieņem divus objektus ar tipiem T
un U
un atgriež jaunu objektu, kas satur abu objektu īpašības. Tas ir jaudīgs veids, kā apvienot datus no dažādiem avotiem.
Vispārīgie ierobežojumi
Kā jau tika parādīts iepriekš, ierobežojumi ļauj sašaurināt tipus, kurus var izmantot ar vispārīgo tipa parametru. Tas nodrošina, ka vispārīgais kods var droši darboties ar norādītajiem tipiem.
interface Lengthwise {
length: number;
}
function loggingIdentity<T extends Lengthwise>(arg: T): T {
console.log(arg.length);
return arg;
}
loggingIdentity([1, 2, 3]); // Output: 3
loggingIdentity("hello"); // Output: 5
// loggingIdentity(123); // Kļūda: Argument of type 'number' is not assignable to parameter of type 'Lengthwise'.
Funkcija loggingIdentity
pieņem argumentu ar tipu T
, kuram jābūt īpašībai length
ar tipu number. Tas nodrošina, ka funkcija var droši piekļūt īpašībai length
.
Vispārīgās klases
Generics var izmantot arī ar klasēm:
class DataStorage<T> {
private data: T[] = [];
addItem(item: T) {
this.data.push(item);
}
removeItem(item: T) {
this.data = this.data.filter(d => d !== item);
}
getItems(): T[] {
return [...this.data];
}
}
const textStorage = new DataStorage<string>();
textStorage.addItem("apple");
textStorage.addItem("banana");
textStorage.removeItem("apple");
console.log(textStorage.getItems()); // Output: [ 'banana' ]
const numberStorage = new DataStorage<number>();
numberStorage.addItem(1);
numberStorage.addItem(2);
numberStorage.removeItem(1);
console.log(numberStorage.getItems()); // Output: [ 2 ]
Klase DataStorage
var glabāt jebkura tipa T
datus. Tas ļauj jums izveidot atkārtoti lietojamas datu struktūras, kas ir tipu drošas.
Vispārīgās saskarnes
Vispārīgās saskarnes ir noderīgas, lai definētu kontraktus, kas var darboties ar dažādiem tipiem. Piemēram:
interface Result<T, E> {
success: boolean;
data?: T;
error?: E;
}
interface User {
id: number;
username: string;
email: string;
}
interface ErrorMessage {
code: number;
message: string;
}
function fetchUser(id: number): Result<User, ErrorMessage> {
if (id === 1) {
return { success: true, data: { id: 1, username: "john.doe", email: "john.doe@example.com" } };
} else {
return { success: false, error: { code: 404, message: "User not found" } };
}
}
const userResult = fetchUser(1);
if (userResult.success) {
console.log(userResult.data.username);
} else {
console.log(userResult.error.message);
}
Saskarne Result
definē vispārīgu struktūru operācijas iznākuma attēlošanai. Tā var saturēt vai nu datus ar tipu T
, vai kļūdu ar tipu E
. Tas ir izplatīts modelis asinhronu operāciju vai operāciju, kas var neizdoties, apstrādei.
Palīgtipi un Generics
TypeScript nodrošina vairākus iebūvētus palīgtipus, kas labi darbojas ar generics. Šie palīgtipi var palīdzēt jums pārveidot un manipulēt ar tipiem jaudīgos veidos.
Partial<T>
Partial<T>
padara visas tipa T
īpašības par neobligātām:
interface Person {
name: string;
age: number;
}
type PartialPerson = Partial<Person>;
const partialPerson: PartialPerson = { name: "Alice" }; // Derīgs
Readonly<T>
Readonly<T>
padara visas tipa T
īpašības par tikai lasāmām:
interface Person {
name: string;
age: number;
}
type ReadonlyPerson = Readonly<Person>;
const readonlyPerson: ReadonlyPerson = { name: "Bob", age: 42 };
// readonlyPerson.age = 43; // Kļūda: Cannot assign to 'age' because it is a read-only property.
Pick<T, K>
Pick<T, K>
atlasa īpašību kopu K
no tipa T
:
interface Person {
name: string;
age: number;
email: string;
}
type NameAndAge = Pick<Person, "name" | "age">;
const nameAndAge: NameAndAge = { name: "Charlie", age: 28 };
Omit<T, K>
Omit<T, K>
noņem īpašību kopu K
no tipa T
:
interface Person {
name: string;
age: number;
email: string;
}
type PersonWithoutEmail = Omit<Person, "email">;
const personWithoutEmail: PersonWithoutEmail = { name: "David", age: 35 };
Record<K, T>
Record<K, T>
izveido tipu ar atslēgām K
un vērtībām ar tipu T
:
type CountryCodes = "US" | "CA" | "UK" | "DE" | "FR" | "JP" | "CN" | "IN" | "BR" | "AU"; // Paplašināts saraksts globālam kontekstam
type Currency = "USD" | "CAD" | "GBP" | "EUR" | "JPY" | "CNY" | "INR" | "BRL" | "AUD"; // Paplašināts saraksts globālam kontekstam
type CurrencyMap = Record<CountryCodes, Currency>;
const currencyMap: CurrencyMap = {
"US": "USD",
"CA": "CAD",
"UK": "GBP",
"DE": "EUR",
"FR": "EUR",
"JP": "JPY",
"CN": "CNY",
"IN": "INR",
"BR": "BRL",
"AU": "AUD",
};
Kartētie tipi
Kartētie tipi ļauj jums pārveidot esošos tipus, iterējot pār to īpašībām. Tas ir jaudīgs veids, kā izveidot jaunus tipus, pamatojoties uz esošajiem. Piemēram, jūs varat izveidot tipu, kas padara visas cita tipa īpašības par tikai lasāmām:
interface Person {
name: string;
age: number;
}
type ReadonlyPerson = {
readonly [K in keyof Person]: Person[K];
};
const readonlyPerson: ReadonlyPerson = { name: "Eve", age: 25 };
// readonlyPerson.age = 26; // Kļūda: Cannot assign to 'age' because it is a read-only property.
Šajā piemērā [K in keyof Person]
iterē pār visām saskarnes Person
atslēgām, un Person[K]
piekļūst katras īpašības tipam. Atslēgvārds readonly
padara katru īpašību par tikai lasāmu.
Nosacījuma tipi
Nosacījuma tipi ļauj jums definēt tipus, pamatojoties uz nosacījumiem. Tas ir jaudīgs veids, kā izveidot tipus, kas pielāgojas dažādiem scenārijiem.
type NonNullable<T> = T extends null | undefined ? never : T;
type MaybeString = string | null | undefined;
type StringType = NonNullable<MaybeString>; // string
function getValue<T>(value: T): NonNullable<T> {
if (value == null) { // Apstrādā gan null, gan undefined
throw new Error("Value cannot be null or undefined");
}
return value as NonNullable<T>;
}
try {
const validValue = getValue("hello");
console.log(validValue.toUpperCase()); // Output: HELLO
const invalidValue = getValue(null); // Šis izsauks kļūdu
console.log(invalidValue); // Šī rinda netiks sasniegta
} catch (error: any) {
console.error(error.message); // Output: Value cannot be null or undefined
}
Šajā piemērā tips NonNullable<T>
pārbauda, vai T
ir null
vai undefined
. Ja tā ir, tas atgriež never
, kas nozīmē, ka tips nav atļauts. Pretējā gadījumā tas atgriež T
. Tas ļauj jums izveidot tipus, kas garantēti nav null vērtības.
Labākā prakse Generics izmantošanai
Šeit ir dažas labākās prakses, kas jāpatur prātā, lietojot generics:
- Izmantojiet aprakstošus tipu parametru nosaukumus: Izvēlieties nosaukumus, kas skaidri norāda tipa parametra mērķi.
- Izmantojiet ierobežojumus, lai sašaurinātu tipus, kurus var izmantot ar vispārīgo tipa parametru: Tas nodrošina, ka jūsu vispārīgais kods var droši darboties ar norādītajiem tipiem.
- Uzturiet savu vispārīgo kodu vienkāršu un koncentrētu: Izvairieties no sava vispārīgā koda pārsarežģīšanas ar pārāk daudziem tipu parametriem vai sarežģītiem ierobežojumiem.
- Rūpīgi dokumentējiet savu vispārīgo kodu: Paskaidrojiet tipu parametru mērķi un visus izmantotos ierobežojumus.
- Apsveriet kompromisus starp koda atkārtotu izmantošanu un tipu drošību: Lai gan generics var uzlabot koda atkārtotu izmantošanu, tie var arī padarīt jūsu kodu sarežģītāku. Pirms generics izmantošanas izvērtējiet priekšrocības un trūkumus.
- Apsveriet lokalizāciju un globalizāciju (l10n un g11n): Strādājot ar datiem, kas jāattēlo lietotājiem dažādos reģionos, pārliecinieties, ka jūsu generics atbalsta atbilstošu formatēšanu un kultūras konvencijas. Piemēram, skaitļu un datumu formatēšana var ievērojami atšķirties starp lokalizācijām.
Piemēri globālā kontekstā
Apskatīsim dažus piemērus, kā generics var izmantot globālā kontekstā:
Valūtas konvertēšana
interface ConversionRate {
rate: number;
fromCurrency: string;
toCurrency: string;
}
function convertCurrency<T extends ConversionRate>(amount: number, rate: T): number {
return amount * rate.rate;
}
const usdToEurRate: ConversionRate = { rate: 0.85, fromCurrency: "USD", toCurrency: "EUR" };
const amountInUSD = 100;
const amountInEUR = convertCurrency(amountInUSD, usdToEurRate);
console.log(`${amountInUSD} USD is equal to ${amountInEUR} EUR`); // Output: 100 USD is equal to 85 EUR
Datuma formatēšana
interface DateFormatOptions {
locale: string;
options: Intl.DateTimeFormatOptions;
}
function formatDate<T extends DateFormatOptions>(date: Date, format: T): string {
return date.toLocaleDateString(format.locale, format.options);
}
const currentDate = new Date();
const usDateFormat: DateFormatOptions = { locale: "en-US", options: { year: 'numeric', month: 'long', day: 'numeric' } };
const germanDateFormat: DateFormatOptions = { locale: "de-DE", options: { year: 'numeric', month: 'long', day: 'numeric' } };
const japaneseDateFormat: DateFormatOptions = { locale: "ja-JP", options: { year: 'numeric', month: 'long', day: 'numeric' } };
console.log("US Date: " + formatDate(currentDate, usDateFormat));
console.log("German Date: " + formatDate(currentDate, germanDateFormat));
console.log("Japanese Date: " + formatDate(currentDate, japaneseDateFormat));
Tulkošanas serviss
interface Translation {
[key: string]: string; // Atļauj dinamiskas valodu atslēgas
}
interface LanguageData<T extends Translation> {
languageCode: string;
translations: T;
}
const englishTranslations: Translation = {
"hello": "Hello",
"goodbye": "Goodbye",
"welcome": "Welcome to our website!"
};
const spanishTranslations: Translation = {
"hello": "Hola",
"goodbye": "Adiós",
"welcome": "¡Bienvenido a nuestro sitio web!"
};
const frenchTranslations: Translation = {
"hello": "Bonjour",
"goodbye": "Au revoir",
"welcome": "Bienvenue sur notre site web !"
};
const languageData: LanguageData<typeof englishTranslations>[] = [
{languageCode: "en", translations: englishTranslations },
{languageCode: "es", translations: spanishTranslations },
{languageCode: "fr", translations: frenchTranslations}
];
function translate<T extends Translation>(key: string, languageCode: string, languageData: LanguageData<T>[]): string {
const lang = languageData.find(lang => lang.languageCode === languageCode);
if (!lang) {
return `Translation for ${key} in ${languageCode} not found.`;
}
return lang.translations[key] || `Translation for ${key} not found.`;
}
console.log(translate("hello", "en", languageData)); // Output: Hello
console.log(translate("hello", "es", languageData)); // Output: Hola
console.log(translate("welcome", "fr", languageData)); // Output: Bienvenue sur notre site web !
console.log(translate("missingKey", "de", languageData)); // Output: Translation for missingKey in de not found.
Noslēgums
TypeScript generics ir jaudīgs rīks, lai rakstītu atkārtoti lietojamu, tipu drošu kodu, kas var darboties ar sarežģītiem datu tipiem. Izprotot generics pamatsintaksi, papildu funkcijas un labāko praksi, jūs varat ievērojami uzlabot savu TypeScript lietojumprogrammu kvalitāti un uzturējamību. Izstrādājot lietojumprogrammas globālai auditorijai, generics var palīdzēt apstrādāt dažādus datu formātus un kultūras konvencijas, nodrošinot nevainojamu lietotāja pieredzi visiem.